/*
 * Copyright (c) 1997, 2019, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package java.io;

import java.nio.file.InvalidPathException;
import java.security.*;
import java.util.Enumeration;
import java.util.List;
import java.util.ArrayList;
import java.util.Vector;
import java.util.Collections;
import sun.security.util.SecurityConstants;

/**
 * 此类表示对文件或目录的访问。FilePermission由路径名和该路径名有效的一组操作组成。
 * <P>
 * 路径名是授予指定操作的文件或目录的路径名。以"/*"结尾的路径名（其中"/"是文件分隔符字符，
 * <code>File.separatorChar</code>）表示该目录中包含的所有文件和目录。以"/-"结尾的路径名表示（递归地）
 * 该目录中包含的所有文件和子目录。由特殊标记"&lt;&lt;ALL FILES&gt;&gt;"组成的路径名匹配<b>任何</b>文件。
 * <P>
 * 注意：由单个"*"组成的路径名表示当前目录中的所有文件，而由单个"-"组成的路径名表示当前目录中的所有文件
 * 以及（递归地）当前目录中包含的所有文件和子目录。
 * <P>
 * 要授予的操作以包含一个或多个逗号分隔关键字的字符串形式传递给构造函数。
 * 可能的关键字是"read"、"write"、"execute"、"delete"和"readlink"。它们的含义定义如下：
 *
 * <DL>
 *    <DT> read <DD> 读取权限
 *    <DT> write <DD> 写入权限
 *    <DT> execute
 *    <DD> 执行权限。允许调用<code>Runtime.exec</code>。对应于<code>SecurityManager.checkExec</code>。
 *    <DT> delete
 *    <DD> 删除权限。允许调用<code>File.delete</code>。对应于<code>SecurityManager.checkDelete</code>。
 *    <DT> readlink
 *    <DD> 读取链接权限。允许通过调用{@link java.nio.file.Files#readSymbolicLink readSymbolicLink}方法
 *         读取<a href="../nio/file/package-summary.html#links">符号链接</a>的目标。
 * </DL>
 * <P>
 * 操作字符串在处理前转换为小写。
 * <P>
 * 授予FilePermissions时要小心。考虑授予对各种文件和目录的读取权限，特别是写入权限的影响。
 * 具有写入操作的"&lt;&lt;ALL FILES&gt;&gt;"权限尤其危险。这授予了对整个文件系统的写入权限。
 * 这实际上允许的一件事是替换系统二进制文件，包括JVM运行时环境。
 *
 * <p>请注意：代码始终可以从其所在目录（或该目录的子目录）读取文件；它不需要显式权限来执行此操作。
 *
 * @see java.security.Permission
 * @see java.security.Permissions
 * @see java.security.PermissionCollection
 *
 * @author Marianne Mueller
 * @author Roland Schemers
 * @since 1.2
 *
 * @serial exclude
 */

public final class FilePermission extends Permission implements Serializable {

    /**
     * 执行操作。
     */
    private final static int EXECUTE = 0x1;
    /**
     * 写入操作。
     */
    private final static int WRITE   = 0x2;
    /**
     * 读取操作。
     */
    private final static int READ    = 0x4;
    /**
     * 删除操作。
     */
    private final static int DELETE  = 0x8;
    /**
     * 读取链接操作。
     */
    private final static int READLINK    = 0x10;

    /**
     * 所有操作（读取、写入、执行、删除、读取链接）
     */
    private final static int ALL     = READ|WRITE|EXECUTE|DELETE|READLINK;
    /**
     * 无操作。
     */
    private final static int NONE    = 0x0;

    // 操作掩码
    private transient int mask;

    // 路径是否表示目录？（通配符或递归）
    private transient boolean directory;

    // 是否为递归目录规范？
    private transient boolean recursive;

    /**
     * 操作字符串。
     *
     * @serial
     */
    private String actions; // 尽可能保持为null，然后
                            // 在getAction函数中创建并重用。

    // 规范化的目录路径。对于目录，
    // 它是不带最后一个字符（"*"或"-"）的名称"/blah/*"或"/blah/-"。

    private transient String cpath;
    private transient boolean invalid;  // 输入路径是否无效
    private transient boolean allFiles; // 是否为<<ALL FILES>>

    // init(int mask)使用的静态字符串
    private static final char RECURSIVE_CHAR = '-';
    private static final char WILD_CHAR = '*';

/*
    public String toString()
    {
        StringBuffer sb = new StringBuffer();
        sb.append("***\n");
        sb.append("cpath = "+cpath+"\n");
        sb.append("mask = "+mask+"\n");
        sb.append("actions = "+getActions()+"\n");
        sb.append("directory = "+directory+"\n");
        sb.append("recursive = "+recursive+"\n");
        sb.append("***\n");
        return sb.toString();
    }
*/

    private static final long serialVersionUID = 7930732926638008763L;

    /**
     * 初始化FilePermission对象。所有构造函数通用。
     * 在反序列化期间也会调用。
     *
     * @param mask 要使用的操作掩码。
     *
     */
    private void init(int mask) {
        if ((mask & ALL) != mask)
                throw new IllegalArgumentException("invalid actions mask");

        if (mask == NONE)
                throw new IllegalArgumentException("invalid actions mask");

        if ((cpath = getName()) == null)
                throw new NullPointerException("name can't be null");

        this.mask = mask;

        if (cpath.equals("<<ALL FILES>>")) {
            allFiles = true;
            directory = true;
            recursive = true;
            cpath = "";
            return;
        }

        if (isPathInvalid()) {
            invalid = true;
            return;
        }

        // store only the canonical cpath if possible
        cpath = AccessController.doPrivileged(new PrivilegedAction<String>() {
            public String run() {
                try {
                    String path = cpath;
                    if (cpath.endsWith("*")) {
                        // 使用替换了通配符字符的路径调用getCanonicalPath
                        // 以避免使用旨在匹配目录中所有条目的路径调用它
                        path = path.substring(0, path.length()-1) + "-";
                        path = new File(path).getCanonicalPath();
                        return path.substring(0, path.length()-1) + "*";
                    } else {
                        return new File(path).getCanonicalPath();
                    }
                } catch (IOException ioe) {
                    return cpath;
                }
            }
        });

        int len = cpath.length();
        char last = ((len > 0) ? cpath.charAt(len - 1) : 0);

        if (last == RECURSIVE_CHAR &&
            cpath.charAt(len - 2) == File.separatorChar) {
            directory = true;
            recursive = true;
            cpath = cpath.substring(0, --len);
        } else if (last == WILD_CHAR &&
            cpath.charAt(len - 2) == File.separatorChar) {
            directory = true;
            //recursive = false;
            cpath = cpath.substring(0, --len);
        } else {
            // 过度处理，因为它们已初始化为false，但
            // 在此注释以提醒我们...
            //directory = false;
            //recursive = false;
        }

        // XXX: 此时路径应该是绝对的。如果不是，是否应该终止？
    }

    // 检查路径的有效性
    private boolean isPathInvalid() {
        if (cpath.indexOf(". ") != -1) {
            try {
                String name = cpath.endsWith("*") ? cpath.substring(0, cpath.length() - 1) + "-" : cpath;
                new File(name).toPath();
            } catch (InvalidPathException ipe) {
                return true;
            }
        }
        return false;
    }

    /**
     * 使用指定操作创建新的FilePermission对象。
     * <i>path</i>是文件或目录的路径名，<i>actions</i>
     * 包含授予文件或目录的所需操作的逗号分隔列表。
     * 可能的操作是"read"、"write"、"execute"、"delete"和"readlink"。
     *
     * <p>以"/*"结尾的路径名（其中"/"是
     * 文件分隔符字符，<code>File.separatorChar</code>）
     * 表示该目录中包含的所有文件和目录。
     * 以"/-"结尾的路径名表示（递归地）该目录中包含的所有文件和
     * 子目录。特殊路径名"&lt;&lt;ALL FILES&gt;&gt;"匹配任何文件。
     *
     * <p>由单个"*"组成的路径名表示当前目录中的所有文件，
     * 而由单个"-"组成的路径名表示当前目录中的所有文件和
     * （递归地）当前目录中包含的所有文件和子目录。
     *
     * <p>包含空字符串的路径名表示空路径。
     *
     * @param path 文件/目录的路径名。
     * @param actions 操作字符串。
     *
     * @throws IllegalArgumentException
     *          如果actions为<code>null</code>、空或包含除指定可能操作之外的操作。
     */
    public FilePermission(String path, String actions) {
        super(path);
        init(getMask(actions));
    }

    /**
     * 使用操作掩码创建新的FilePermission对象。
     * 比FilePermission(String, String)构造函数更高效。
     * 可以在需要创建FilePermission对象以传递给
     * <code>implies</code>方法的代码中使用。
     *
     * @param path 文件/目录的路径名。
     * @param mask 要使用的操作掩码。
     */

    // 包私有，供FilePermissionCollection add方法使用
    FilePermission(String path, int mask) {
        super(path);
        init(mask);
    }

    /**
     * 检查此FilePermission对象是否"隐含"指定的权限。
     * <P>
     * 更具体地说，如果满足以下条件，此方法返回true：
     * <ul>
     * <li> <i>p</i>是FilePermission的实例，
     * <li> <i>p</i>的操作是此对象操作的真子集，并且
     * <li> <i>p</i>的路径名被此对象的路径名隐含。
     *      例如，"/tmp/*"隐含"/tmp/foo"，因为
     *      "/tmp/*"包含"/tmp"目录中的所有文件，
     *      包括名为"foo"的文件。
     * </ul>
     *
     * @param p 要检查的权限。
     *
     * @return 如果指定的权限不为<code>null</code>且被此对象隐含，
     *         则返回<code>true</code>，否则返回<code>false</code>。
     */
    public boolean implies(Permission p) {
        if (!(p instanceof FilePermission))
            return false;

        FilePermission that = (FilePermission) p;

        // 我们获得有效掩码。即this和that的"与"运算。
        // 它们必须等于that.mask才能使implies返回true。

        return ((this.mask & that.mask) == that.mask) && impliesIgnoreMask(that);
    }

    /**
     * 检查Permission的操作是否是此对象操作的真子集。
     * 当且仅当此FilePermission的路径也隐含那个FilePermission的路径时，
     * 返回有效掩码。
     *
     * @param that 要检查的FilePermission。
     * @return 有效掩码
     */
    boolean impliesIgnoreMask(FilePermission that) {
        if (this == that) {
            return true;
        }
        if (allFiles) {
            return true;
        }
        if (this.invalid || that.invalid) {
            return false;
        }
        if (that.allFiles) {
            return false;
        }
        if (this.directory) {
            if (this.recursive) {
                // 确保that.path比path长，这样
                // 像/foo/-这样的东西不会隐含/foo
                if (that.directory) {
                    return (that.cpath.length() >= this.cpath.length()) &&
                            that.cpath.startsWith(this.cpath);
                }  else {
                    return ((that.cpath.length() > this.cpath.length()) &&
                        that.cpath.startsWith(this.cpath));
                }
            } else {
                if (that.directory) {
                    // 如果传入的权限是目录规范，
                    // 确保非递归权限（即此对象）不能隐含递归权限。
                    if (that.recursive)
                        return false;
                    else
                        return (this.cpath.equals(that.cpath));
                } else {
                    int last = that.cpath.lastIndexOf(File.separatorChar);
                    if (last == -1)
                        return false;
                    else {
                        // this.cpath.equals(that.cpath.substring(0, last+1));
                        // 使用regionMatches避免创建新字符串
                        return (this.cpath.length() == (last + 1)) &&
                            this.cpath.regionMatches(0, that.cpath, 0, last+1);
                    }
                }
            }
        } else if (that.directory) {
            // 如果这不是递归/通配符，
            // 不要让它隐含递归/通配符权限
            return false;
        } else {
            return (this.cpath.equals(that.cpath));
        }
    }

    /**
     * 检查两个FilePermission对象是否相等。检查<i>obj</i>是否为
     * FilePermission，并且具有与此对象相同的路径名和操作。
     *
     * @param obj 我们正在测试与此对象相等性的对象。
     * @return 如果obj是FilePermission，并且具有与此FilePermission对象
     *         相同的路径名和操作，则返回<code>true</code>，否则返回<code>false</code>。
     */
    public boolean equals(Object obj) {
        if (obj == this)
            return true;

        if (! (obj instanceof FilePermission))
            return false;

        FilePermission that = (FilePermission) obj;

        if (this.invalid || that.invalid) {
            return false;
        }
        return (this.mask == that.mask) &&
               (this.allFiles == that.allFiles) &&
            this.cpath.equals(that.cpath) &&
            (this.directory == that.directory) &&
            (this.recursive == that.recursive);
    }

    /**
     * 返回此对象的哈希码值。
     *
     * @return 此对象的哈希码值。
     */
    public int hashCode() {
        return 0;
    }

    /**
     * 将操作字符串转换为操作掩码。
     *
     * @param actions 操作字符串。
     * @return 操作掩码。
     */
    private static int getMask(String actions) {
        int mask = NONE;

        // 空操作有效吗？
        if (actions == null) {
            return mask;
        }

        // 使用对象标识比较已知的内部字符串以获得性能优势
        // （这些值在JDK内部大量使用）。
        if (actions == SecurityConstants.FILE_READ_ACTION) {
            return READ;
        } else if (actions == SecurityConstants.FILE_WRITE_ACTION) {
            return WRITE;
        } else if (actions == SecurityConstants.FILE_EXECUTE_ACTION) {
            return EXECUTE;
        } else if (actions == SecurityConstants.FILE_DELETE_ACTION) {
            return DELETE;
        } else if (actions == SecurityConstants.FILE_READLINK_ACTION) {
            return READLINK;
        }

        char[] a = actions.toCharArray();

        int i = a.length - 1;
        if (i < 0)
            return mask;

        while (i != -1) {
            char c;

            // 跳过空白字符
            while ((i!=-1) && ((c = a[i]) == ' ' ||
                               c == '\r' ||
                               c == '\n' ||
                               c == '\f' ||
                               c == '\t'))
                i--;

            // 检查已知字符串
            int matchlen;

            if (i >= 3 && (a[i-3] == 'r' || a[i-3] == 'R') &&
                          (a[i-2] == 'e' || a[i-2] == 'E') &&
                          (a[i-1] == 'a' || a[i-1] == 'A') &&
                          (a[i] == 'd' || a[i] == 'D'))
            {
                matchlen = 4;
                mask |= READ;

            } else if (i >= 4 && (a[i-4] == 'w' || a[i-4] == 'W') &&
                                 (a[i-3] == 'r' || a[i-3] == 'R') &&
                                 (a[i-2] == 'i' || a[i-2] == 'I') &&
                                 (a[i-1] == 't' || a[i-1] == 'T') &&
                                 (a[i] == 'e' || a[i] == 'E'))
            {
                matchlen = 5;
                mask |= WRITE;

            } else if (i >= 6 && (a[i-6] == 'e' || a[i-6] == 'E') &&
                                 (a[i-5] == 'x' || a[i-5] == 'X') &&
                                 (a[i-4] == 'e' || a[i-4] == 'E') &&
                                 (a[i-3] == 'c' || a[i-3] == 'C') &&
                                 (a[i-2] == 'u' || a[i-2] == 'U') &&
                                 (a[i-1] == 't' || a[i-1] == 'T') &&
                                 (a[i] == 'e' || a[i] == 'E'))
            {
                matchlen = 7;
                mask |= EXECUTE;

            } else if (i >= 5 && (a[i-5] == 'd' || a[i-5] == 'D') &&
                                 (a[i-4] == 'e' || a[i-4] == 'E') &&
                                 (a[i-3] == 'l' || a[i-3] == 'L') &&
                                 (a[i-2] == 'e' || a[i-2] == 'E') &&
                                 (a[i-1] == 't' || a[i-1] == 'T') &&
                                 (a[i] == 'e' || a[i] == 'E'))
            {
                matchlen = 6;
                mask |= DELETE;

            } else if (i >= 7 && (a[i-7] == 'r' || a[i-7] == 'R') &&
                                 (a[i-6] == 'e' || a[i-6] == 'E') &&
                                 (a[i-5] == 'a' || a[i-5] == 'A') &&
                                 (a[i-4] == 'd' || a[i-4] == 'D') &&
                                 (a[i-3] == 'l' || a[i-3] == 'L') &&
                                 (a[i-2] == 'i' || a[i-2] == 'I') &&
                                 (a[i-1] == 'n' || a[i-1] == 'N') &&
                                 (a[i] == 'k' || a[i] == 'K'))
            {
                matchlen = 8;
                mask |= READLINK;

            } else {
                // 解析错误
                throw new IllegalArgumentException(
                        "invalid permission: " + actions);
            }

            // 确保我们没有只匹配单词的尾部
            // 如"ackbarfaccept"。同时，跳到逗号。
            boolean seencomma = false;
            while (i >= matchlen && !seencomma) {
                switch(a[i-matchlen]) {
                case ',':
                    seencomma = true;
                    break;
                case ' ': case '\r': case '\n':
                case '\f': case '\t':
                    break;
                default:
                    throw new IllegalArgumentException(
                            "invalid permission: " + actions);
                }
                i--;
            }

            // 将i指向逗号位置减一（或-1）。
            i -= matchlen;
        }

        return mask;
    }

    /**
     * 返回当前操作掩码。由FilePermissionCollection使用。
     *
     * @return 操作掩码。
     */
    int getMask() {
        return mask;
    }

    /**
     * 返回操作的规范字符串表示。
     * 始终按以下顺序返回存在的操作：
     * read、write、execute、delete、readlink。
     *
     * @return 操作的规范字符串表示。
     */
    private static String getActions(int mask) {
        StringBuilder sb = new StringBuilder();
        boolean comma = false;

        if ((mask & READ) == READ) {
            comma = true;
            sb.append("read");
        }

        if ((mask & WRITE) == WRITE) {
            if (comma) sb.append(',');
            else comma = true;
            sb.append("write");
        }

        if ((mask & EXECUTE) == EXECUTE) {
            if (comma) sb.append(',');
            else comma = true;
            sb.append("execute");
        }

        if ((mask & DELETE) == DELETE) {
            if (comma) sb.append(',');
            else comma = true;
            sb.append("delete");
        }

        if ((mask & READLINK) == READLINK) {
            if (comma) sb.append(',');
            else comma = true;
            sb.append("readlink");
        }

        return sb.toString();
    }

    /**
     * 返回操作的"规范字符串表示"。
     * 也就是说，此方法始终按以下顺序返回存在的操作：
     * read、write、execute、delete、readlink。例如，如果此FilePermission
     * 对象允许写入和读取操作，调用<code>getActions</code>
     * 将返回字符串"read,write"。
     *
     * @return 操作的规范字符串表示。
     */
    public String getActions() {
        if (actions == null)
            actions = getActions(this.mask);

        return actions;
    }

    /**
     * 返回用于存储FilePermission对象的新PermissionCollection对象。
     * <p>
     * FilePermission对象必须以允许它们以任何顺序插入集合的方式存储，
     * 但也要使PermissionCollection <code>implies</code>
     * 方法能够以高效（且一致）的方式实现。
     *
     * <p>例如，如果您有两个FilePermissions：
     * <OL>
     * <LI>  <code>"/tmp/-", "read"</code>
     * <LI>  <code>"/tmp/scratch/foo", "write"</code>
     * </OL>
     *
     * <p>并且您使用FilePermission调用<code>implies</code>方法：
     *
     * <pre>
     *   "/tmp/scratch/foo", "read,write",
     * </pre>
     *
     * 那么<code>implies</code>函数必须
     * 考虑"/tmp/-"和"/tmp/scratch/foo"
     * 权限，因此有效权限是"read,write"，
     * 并且<code>implies</code>返回true。FilePermissions的"implies"语义
     * 由此<code>newPermissionCollection</code>方法返回的PermissionCollection对象正确处理。
     *
     * @return 适合存储FilePermissions的新PermissionCollection对象。
     */
    public PermissionCollection newPermissionCollection() {
        return new FilePermissionCollection();
    }

    /**
     * 调用WriteObject将FilePermission的状态保存到流中。
     * 操作被序列化，超类负责处理名称。
     */
    private void writeObject(ObjectOutputStream s)
        throws IOException
    {
        // 写出操作。超类负责处理名称
        // 调用getActions确保actions字段已初始化
        if (actions == null)
            getActions();
        s.defaultWriteObject();
    }

    /**
     * 调用readObject从流中恢复FilePermission的状态。
     */
    private void readObject(ObjectInputStream s)
         throws IOException, ClassNotFoundException
    {
        // 读入操作，然后通过调用init恢复其他所有内容。
        s.defaultReadObject();
        init(getMask(actions));
    }
}

/**
 * FilePermissionCollection存储一组FilePermission权限。
 * FilePermission对象必须以允许它们以任何顺序插入的方式存储，
 * 但要使implies函数能够评估implies方法。
 * 例如，如果您有两个FilePermissions：
 * <OL>
 * <LI> "/tmp/-", "read"
 * <LI> "/tmp/scratch/foo", "write"
 * </OL>
 * 并且您使用FilePermission调用implies函数：
 * "/tmp/scratch/foo", "read,write"，那么implies函数必须
 * 考虑/tmp/-和/tmp/scratch/foo
 * 权限，因此有效权限是"read,write"。
 *
 * @see java.security.Permission
 * @see java.security.Permissions
 * @see java.security.PermissionCollection
 *
 *
 * @author Marianne Mueller
 * @author Roland Schemers
 *
 * @serial include
 *
 */

final class FilePermissionCollection extends PermissionCollection
    implements Serializable
{
    // 不序列化；请参见类末尾的序列化部分
    private transient List<Permission> perms;

    /**
     * 创建一个空的FilePermissionCollection对象。
     */
    public FilePermissionCollection() {
        perms = new ArrayList<>();
    }

    /**
     * 向FilePermissionCollection添加权限。哈希的键是
     * permission.path。
     *
     * @param permission 要添加的Permission对象。
     *
     * @exception IllegalArgumentException - 如果权限不是FilePermission
     *
     * @exception SecurityException - 如果此FilePermissionCollection对象
     *                                已标记为只读
     */
    public void add(Permission permission) {
        if (! (permission instanceof FilePermission))
            throw new IllegalArgumentException("invalid permission: "+
                                               permission);
        if (isReadOnly())
            throw new SecurityException(
                "attempt to add a Permission to a readonly PermissionCollection");

        synchronized (this) {
            perms.add(permission);
        }
    }

    /**
     * 检查此权限集是否隐含"permission"中表达的权限。
     *
     * @param permission 要比较的Permission对象
     *
     * @return 如果"permission"是集合中权限的真子集，则返回true，否则返回false。
     */
    public boolean implies(Permission permission) {
        if (! (permission instanceof FilePermission))
            return false;

        FilePermission fp = (FilePermission) permission;

        int desired = fp.getMask();
        int effective = 0;
        int needed = desired;

        synchronized (this) {
            int len = perms.size();
            for (int i = 0; i < len; i++) {
                FilePermission x = (FilePermission) perms.get(i);
                if (((needed & x.getMask()) != 0) && x.impliesIgnoreMask(fp)) {
                    effective |=  x.getMask();
                    if ((effective & desired) == desired)
                        return true;
                    needed = (desired ^ effective);
                }
            }
        }
        return false;
    }

    /**
     * 返回容器中所有FilePermission对象的枚举。
     *
     * @return 所有FilePermission对象的枚举。
     */
    public Enumeration<Permission> elements() {
        // 将Iterator转换为Enumeration
        synchronized (this) {
            return Collections.enumeration(perms);
        }
    }

    private static final long serialVersionUID = 2202956749081564585L;

    // 需要与早期版本保持序列化互操作性，
    // 早期版本具有可序列化字段：
    //    private Vector permissions;

    /**
     * @serialField permissions java.util.Vector
     *     FilePermission对象列表。
     */
    private static final ObjectStreamField[] serialPersistentFields = {
        new ObjectStreamField("permissions", Vector.class),
    };

    /**
     * @serialData "permissions"字段（包含FilePermissions的Vector）。
     */
    /*
     * 将perms字段的内容作为Vector写出，
     * 以与早期版本保持序列化兼容性。
     */
    private void writeObject(ObjectOutputStream out) throws IOException {
        // 不要调用out.defaultWriteObject()

        // 写出Vector
        Vector<Permission> permissions = new Vector<>(perms.size());
        synchronized (this) {
            permissions.addAll(perms);
        }

        ObjectOutputStream.PutField pfields = out.putFields();
        pfields.put("permissions", permissions);
        out.writeFields();
    }

    /*
     * 读入FilePermissions的Vector并将它们保存在perms字段中。
     */
    private void readObject(ObjectInputStream in)
        throws IOException, ClassNotFoundException
    {
        // 不要调用defaultReadObject()

        // 读入序列化字段
        ObjectInputStream.GetField gfields = in.readFields();

        // 获取我们想要的那个
        @SuppressWarnings("unchecked")
        Vector<Permission> permissions = (Vector<Permission>)gfields.get("permissions", null);
        perms = new ArrayList<>(permissions.size());
        for (Permission perm : permissions) {
            perms.add(perm);
        }
    }
}
